package com.tanfuqi.sicent.client

import com.tanfuqi.sicent.client.filter.BridgeFilter
import com.tanfuqi.sicent.client.filter.DatabaseFilter
import com.tanfuqi.sicent.protocol.ProtocolCodecFactory
import com.tanfuqi.sicent.protocol.message.Message
import com.tanfuqi.sicent.protocol.message.ResponseBody
import com.tanfuqi.sicent.protocol.message.ShopInfo
import org.apache.mina.core.filterchain.IoFilter
import org.apache.mina.core.filterchain.IoFilterAdapter
import org.apache.mina.core.service.IoConnector
import org.apache.mina.core.service.IoHandlerAdapter
import org.apache.mina.core.session.IoSession
import org.apache.mina.filter.codec.ProtocolCodecFilter
import org.apache.mina.filter.logging.LoggingFilter
import org.apache.mina.transport.socket.nio.NioSocketConnector
import org.mybatis.spring.annotation.MapperScan
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.publisher.toMono
import java.net.InetSocketAddress
import java.net.SocketAddress
import java.time.Duration


@SpringBootApplication
@MapperScan(basePackages = ["com.tanfuqi.sicent.client.mapper"])
//@RestController
open class Application {

    @Value("\${shopId}")
    val shopId: String? = null
    @Value("\${host}")
    var host: String? = null
    private var sicentAddress: SocketAddress? = null
    private var serverAddress:SocketAddress = InetSocketAddress("registry.yxlmwk.com", 11018)

    @Bean
    open fun connector(@Autowired databaseFilter: DatabaseFilter): IoConnector = Mono.justOrEmpty<IoConnector>(NioSocketConnector()).doOnSuccess { connector ->
        sicentAddress = InetSocketAddress(
                host, 11018)
        connector.filterChain.addLast("codec", ProtocolCodecFilter(ProtocolCodecFactory))
        connector.filterChain.addLast("logger", LoggingFilter())
        connector.filterChain.addLast("database", databaseFilter)
        connector.filterChain.addLast("bridgeFilter", object : BridgeFilter(sicentAddress!!, serverAddress) {
            override fun messageReceived(nextFilter: IoFilter.NextFilter?, session: IoSession?, message: Any?) {
                if (message is Message && 0x80008001L.toInt() == message.type) {
                    if (0 == ((message.body as? Map<*, *>)?.get("head")as? Map<*, *>)?.get("errcode"))
                        message.body = ResponseBody(body = ShopInfo(shopId/*"5b5c4d4cd9fb83ce9897c550"*/))
                }


                super.messageReceived(nextFilter, session, message)
            }
        })
        connector.filterChain.addLast("reconnect", object : IoFilterAdapter() {
            override fun sessionClosed(nextFilter: IoFilter.NextFilter?, session: IoSession?) {
                if (session?.remoteAddress == sicentAddress)
                    connector.managedSessions.values.filter { it.remoteAddress != session?.remoteAddress }.map { it.close(false) }
                Mono.justOrEmpty<SocketAddress>(session?.remoteAddress).delayElement(Duration.ofSeconds(15)).flatMap {
                    connector.toMono().getSession(it)
                }.retry().subscribe()
                super.sessionClosed(nextFilter, session)
            }
        })
        connector.handler = IoHandlerAdapter()
    }.doOnSuccess { connector ->
        Flux.just(serverAddress).flatMap {
            connector.toMono().getSession(it)
        }.subscribe()
    }.block()!!


//
//
//    @GetMapping("test")
//    fun test(): Any? = Mono.justOrEmpty<OnlineMapper>(onlineMapper).map { it.getOnlineCardIdByComputerName("%003") }
}

fun Mono<IoConnector>.getSession(socketAddress: SocketAddress): Mono<IoSession> =
        this.flatMap { service ->
            socketAddress.toMono().flatMap { address ->
                Mono.justOrEmpty<IoSession>(service.managedSessions.values.find { session ->
                    session.remoteAddress == address
                }).switchIfEmpty(
                        address.toMono().map {
                            service.connect(address).awaitUninterruptibly().session
                        }.doOnSuccess {
                            println("new session:$it")
                        }
                )
            }
        }

fun main(args: Array<String>) {
    SpringApplication.run(Application::class.java, *args)

}
